package callgraph.process;

import callgraph.asm.ClassVisitor;
import callgraph.domain.JarConfig;
import callgraph.domain.ProfilingFilter;
import callgraph.dto.ExtendsClassMethodInfo;
import callgraph.dto.MethodAttribute;
import callgraph.dto.MethodCallDto;
import callgraph.util.CommonUtil;
import callgraph.util.JavaCGConstants;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import org.apache.bcel.classfile.ClassParser;
import org.apache.bcel.classfile.JavaClass;
import org.apache.bcel.classfile.Method;
import org.apache.commons.lang3.ArrayUtils;

import java.io.BufferedWriter;
import java.io.IOException;
import java.util.Enumeration;
import java.util.List;
import java.util.Map;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

class HandleJar extends CallGraphProcessor {

    public HandleJar(CallGraphProcessStorage storage, ProfilingFilter profilingFilter, JarConfig jarConfig, JarFile jarFile, BufferedWriter resultWriter) {
        super(storage, profilingFilter, jarConfig, jarFile, resultWriter);
    }

    @Override
    public boolean handle() {
        Enumeration<JarEntry> enumeration = jarFile.entries();
        while (enumeration.hasMoreElements()) {
            JarEntry jarEntry = enumeration.nextElement();
            if (jarEntry.isDirectory() || !jarEntry.getName().endsWith(".class")) {
                continue;
            }
            if (!profilingFilter.needInject(jarEntry.getName())) {
                continue;
            }
            try {
                handleOneClass(jarConfig, jarEntry);
            } catch (IOException e) {
                e.printStackTrace();
                return false;
            }
        }
        return true;
    }


    private void handleOneClass(JarConfig jarConfig, JarEntry jarEntry) throws IOException {
        ClassParser cp = new ClassParser(jarConfig.getJarUrl(), jarEntry.getName());
        JavaClass javaClass = cp.parse();

        // 查找涉及继承的类的信息
        if (javaClass.isClass() && storage.getExtendsClassesSet().contains(javaClass.getClassName())) {
            findExtendsClassesInfo(javaClass);
        }

        ClassVisitor classVisitor = initVisitor(javaClass);
        classVisitor.start();

        List<MethodCallDto> methodCalls = classVisitor.methodCalls();
        for (MethodCallDto methodCallDto : methodCalls) {
            writeResult(methodCallDto.getMethodCall());
            if (methodCallDto.getSourceLine() != JavaCGConstants.NONE_LINE_NUMBER) {
                writeResult(" " + methodCallDto.getSourceLine());
                writeResult(" " + jarNum);
            }
            writeResult(JavaCGConstants.NEW_LINE);
        }
        storage.getClassCallerMethodMap().put(javaClass.getClassName(), classVisitor.methodCalls());
    }

    private void findExtendsClassesInfo(JavaClass javaClass) {
        String className = javaClass.getClassName();
        if (storage.getExtendsClassMethodInfoMap().containsKey(className)) {
            return;
        }
        String superClassName = javaClass.getSuperclassName();
        if (profilingFilter.needInject(superClassName)) {
            if (storage.getChildrenClassInfoMap().containsKey(superClassName)) {
                storage.getChildrenClassInfoMap().get(superClassName).add(className);
            } else {
                List<String> newChildrenClassInfoList = Lists.newArrayList();
                newChildrenClassInfoList.add(className);
                storage.getChildrenClassInfoMap().put(superClassName, newChildrenClassInfoList);
            }
        }
        ExtendsClassMethodInfo extendsClassMethodInfo = ExtendsClassMethodInfo.builder()
                .abstractClass(javaClass.isAbstract())
                .superClassName(superClassName)
                .methodAttributeMap(generateMethodAttributeMap(javaClass))
                .build();
        storage.getExtendsClassMethodInfoMap().put(className, extendsClassMethodInfo);
    }

    private Map<String, MethodAttribute> generateMethodAttributeMap(JavaClass javaClass) {
        Map<String, MethodAttribute> methodAttributeMap = Maps.newHashMap();
        Method[] methods = javaClass.getMethods();
        if (ArrayUtils.isNotEmpty(methods)) {
            for (Method method : methods) {
                String methodName = method.getName();
                if (!methodName.startsWith("<") && !method.isStatic()
                        && (method.isAbstract() || method.isPublic() || method.isProtected())) {
                    MethodAttribute methodAttribute = MethodAttribute.builder()
                            .abstractMethod(method.isAbstract())
                            .publicMethod(method.isPublic())
                            .protectedMethod(method.isProtected())
                            .build();
                    String methodWithArgs = methodName + CommonUtil.argumentList(method.getArgumentTypes());
                    methodAttributeMap.put(methodWithArgs, methodAttribute);
                }
            }
        }
        return methodAttributeMap;
    }


    private ClassVisitor initVisitor(JavaClass javaClass) {
        ClassVisitor classVisitor = new ClassVisitor(profilingFilter, javaClass);
        classVisitor.setCalleeMethodMap(storage.getCalleeMethodMapGlobal());
        classVisitor.setRunnableImplClassMap(storage.getRunnableImplClassMap());
        classVisitor.setCallableImplClassMap(storage.getCallableImplClassMap());
        classVisitor.setThreadChildClassMap(storage.getThreadChildClassMap());
        classVisitor.setMethodAnnotationMap(storage.getMethodAnnotationMap());
        classVisitor.setCallIdCounter(storage.getCallIdCounter());
        return classVisitor;
    }

    @Override
    public CallGraphProcessor next() {
        return new AddInterfaceMethod4SuperClass(storage, profilingFilter, jarConfig, jarFile, resultWriter);
    }
}
